#include <string.h>
#include "redtupleobject.h"
#include "redcfunctionobject.h"
#include "redexceptionobject.h"

RedTypeObject RedType_CFunction =
{
	RedType_INIT("c_func", RedCFunctionObject, RedType_Object)
};

RedObject *RedCFunction_WrapVarFunc(RedCUnaryFunction func)
{
	RedObject *result = RedObject_Create(&RedType_CFunction, NULL);

	if (!result)
		return NULL;

	RedCFunction_Type(result) = cf_vfunc;
	RedCFunction_Unary(result) = func;
	return result;
}

RedObject *RedCFunction_WrapVarMethod(RedCBinaryFunction method)
{
	RedObject *result = RedObject_Create(&RedType_CFunction, NULL);

	if (!result)
		return NULL;

	RedCFunction_Type(result) = cf_vmethod;
	RedCFunction_Binary(result) = method;
	return result;
}

RedObject *RedCFunction_WrapUnary(RedCUnaryFunction unary)
{
	RedObject *result = RedObject_Create(&RedType_CFunction, NULL);

	if (!result)
		return NULL;

	RedCFunction_Type(result) = cf_unary;
	RedCFunction_Unary(result) = unary;
	return result;
}

RedObject *RedCFunction_WrapBinary(RedCBinaryFunction binary)
{
	RedObject *result = RedObject_Create(&RedType_CFunction, NULL);

	if (!result)
		return NULL;

	RedCFunction_Type(result) = cf_binary;
	RedCFunction_Binary(result) = binary;
	return result;
}

RedObject *RedCFunction_WrapTernary(RedCTernaryFunction ternary)
{
	RedObject *result = RedObject_Create(&RedType_CFunction, NULL);

	if (!result)
		return NULL;

	RedCFunction_Type(result) = cf_ternary;
	RedCFunction_Ternary(result) = ternary;
	return result;
}

RedObject *RedCFunction_WrapNullary(RedCNullaryFunction nullary)
{
	RedObject *result = RedObject_Create(&RedType_CFunction, NULL);

	if (!result)
		return NULL;

	RedCFunction_Type(result) = cf_nullary;
	RedCFunction_Nullary(result) = nullary;
	return result;
}

RedObject *RedCFunction_Invoke(RedObject *self, RedObject *args)
{
	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
		return NULL;
	}

	if (!RedCFunction_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'c_func' expected for 'self' argument");
		return NULL;
	}

	switch (RedCFunction_Type(self))
	{
		case cf_vfunc:
			return RedCFunction_Unary(self)(args);

		case cf_vmethod:
		{
			if (!RedTuple_Length(args))
			{
				RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at least 1 argument, but no args given");
				return NULL;
			}

			RedObject *arg0 = RedTuple_Items(args)[0];
			RedObject *remains = RedTuple_FromLength(RedTuple_Length(args) - 1);

			for (int i = 1; i < RedTuple_Length(args); i++)
			{
				Red_REF(RedTuple_Items(args)[i]);
				RedTuple_Items(remains)[i - 1] = RedTuple_Items(args)[i];
			}

			RedObject *result = RedCFunction_Binary(self)(arg0, remains);

			Red_UNREF(remains);
			return result;
		}

		case cf_unary:
		{
			if (RedTuple_Length(args) != 1)
			{
				RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at exactly 1 argument, but %d given", RedTuple_Length(args));
				return NULL;
			}

			RedObject *arg0 = RedTuple_Items(args)[0];
			return RedCFunction_Unary(self)(arg0);
		}

		case cf_binary:
		{
			if (RedTuple_Length(args) != 2)
			{
				RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at exactly 2 arguments, but %d given", RedTuple_Length(args));
				return NULL;
			}

			RedObject *arg0 = RedTuple_Items(args)[0];
			RedObject *arg1 = RedTuple_Items(args)[1];
			return RedCFunction_Binary(self)(arg0, arg1);
		}

		case cf_ternary:
		{
			if (RedTuple_Length(args) != 3)
			{
				RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at exactly 3 arguments, but %d given", RedTuple_Length(args));
				return NULL;
			}

			RedObject *arg0 = RedTuple_Items(args)[0];
			RedObject *arg1 = RedTuple_Items(args)[1];
			RedObject *arg2 = RedTuple_Items(args)[2];
			return RedCFunction_Ternary(self)(arg0, arg1, arg2);
		}

		case cf_nullary:
		{
			if (!RedTuple_Length(args))
				return RedCFunction_Nullary(self)();

			RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at no arguments");
			return NULL;
		}

		default:
		{
			RedException_ThrowFormat(&RedExc_InternalError, "Impossible value for CFunction.type");
			return NULL;
		}
	}

#undef get_argument
}
